package svcmng

import (
	"fmt"
	"sync"

	"starsine.cn/appTpl/cmn"
	. "starsine.cn/appTpl/cmn/defs"
	"starsine.cn/appTpl/cmn/logs"
	"starsine.cn/appTpl/cmn/timermgr"
	"starsine.cn/appTpl/svccenter/registry/registrypubsub"
	. "starsine.cn/appTpl/svccenter/registry/svcmng/iservice"
	"starsine.cn/appTpl/svcregistry/grpc/svcregistryproto"
)

type ServiceMap struct {
	rpcMode bool
	data    map[string]IService
	sync.RWMutex
}

func CreateServiceMap(rpcMode bool) *ServiceMap {
	p := new(ServiceMap)

	p.rpcMode = rpcMode
	p.data = make(map[string]IService)

	return p
}

func (p *ServiceMap) addService(vKey string, svc IService) {
	p.RWMutex.Lock()
	defer p.RWMutex.Unlock()

	p.data[vKey] = svc
}

func (p *ServiceMap) delService(vKey string) {
	p.RWMutex.Lock()
	defer p.RWMutex.Unlock()

	registrypubsub.GetPubSub().ClearSub(vKey)
	delete(p.data, vKey)
}

func (p *ServiceMap) getService(vKey string) (IService, bool) {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	svc, ok := p.data[vKey]

	return svc, ok
}

func (p *ServiceMap) getServiceList() []IService {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	svcs := []IService{}

	for _, v := range p.data {
		svcs = append(svcs, v)
	}

	return svcs
}

func (p *ServiceMap) getServiceListByType(vType string) []IService {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	svcs := []IService{}

	for _, v := range p.data {
		if v.GetServiceType() == vType {
			svcs = append(svcs, v)
		}
	}

	return svcs
}

func (p *ServiceMap) forEach(f func(k string, v IService) bool) {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	for k, v := range p.data {
		if !f(k, v) {
			break
		}
	}
}

func (p *ServiceMap) dump() (str string) {
	p.RWMutex.RLock()
	defer p.RWMutex.RUnlock()

	str += fmt.Sprintln("ServiceMap:")
	for k, v := range p.data {
		str += fmt.Sprintln("----------------------------")
		str += fmt.Sprintln("key: ", k)
		str += fmt.Sprintln(v)
	}

	return str
}

type ServiceMng struct {
	appCtxt *AppContext
	tmgr    *timermgr.TimerMgr
	*ServiceMap
	recycleTimerID int64
}

func CreateServiceMng(appCtxt *AppContext, rpcMode bool) *ServiceMng {
	p := new(ServiceMng)

	p.appCtxt = appCtxt
	p.tmgr = cmn.GetTm()
	p.ServiceMap = CreateServiceMap(rpcMode)

	callbackFunc := timermgr.NewOnTimeOut(p.recycleService)
	p.recycleTimerID = p.tmgr.AddPeriodTimer(3, callbackFunc)
	return p
}

func (p *ServiceMng) DumpService() string {
	return p.dump()
}

func (p *ServiceMng) GetService(vType, vName, userSpace string) (IService, bool) {
	instanceID := SvcInstanceID(vType, vName, userSpace)
	return p.GetServiceByInstanceID(instanceID)
}

func (p *ServiceMng) GetServiceByInstanceID(instanceID string) (IService, bool) {
	return p.ServiceMap.getService(instanceID)
}

func (p *ServiceMng) GetServiceList() []IService {
	return p.getServiceList()
}

func (p *ServiceMng) GetServiceListByType(vType string) []IService {
	return p.getServiceListByType(vType)
}

func (p *ServiceMng) ServiceReg(vType, vName, userSpace string, data SvcData) (IService, error) {
	s, ok := p.GetService(vType, vName, userSpace)
	if ok {
		logs.Warnf("ServiceReg an exist service, instance id: %s", s.GetInstanceID())
		p.ServiceUnreg(vType, vName, userSpace)
	}

	var service IService = nil
	if p.rpcMode {
		service = CreateGRpcService(vType, vName, userSpace, data, p.appCtxt, p.tmgr)
	} else {
		service = CreateWsService(vType, vName, userSpace, data, p.appCtxt, p.tmgr)
	}
	p.addService(service.GetInstanceID(), service)

	registrypubsub.GetPubSub().Event(
		fmt.Sprintf("svc-register?user-space=%s;svc-type=%s", userSpace, vType),
		[]*svcregistryproto.SvcAddr{{
			InstanceID: service.GetInstanceID(),
			RpcAddr:    data.RpcAddr,
		}},
	)
	registrypubsub.GetPubSub().Event(
		fmt.Sprintf("svc-update?instance-id=*;action=*"),
		&svcregistryproto.SvcUpdateInfo{
			InstanceID: service.GetInstanceID(),
			Action:     "on",
		},
	)

	logs.Debugf("ServiceReg: type=%s, name=%s, userspace=%s, InstanceID = %s", vType, vName, userSpace, service.GetInstanceID())

	return service, nil
}

func (p *ServiceMng) ServiceUnreg(vType, vName, userSpace string) error {
	svc, ok := p.GetService(vType, vName, userSpace)
	if !ok {
		logs.Errorf("unregister an unexist service: type=%s, name=%s, userspace=%s", vType, vName, userSpace)
		return fmt.Errorf("unregister an unexist service: type=%s, name=%s, userspace=%s", vType, vName, userSpace)
	}

	svc.End()
	p.delService(svc.GetInstanceID())

	logs.Debugf("ServiceUnreg: type=%s, name=%s, userspace=%s", vType, vName, userSpace)
	return nil
}

func (p *ServiceMng) ServiceUnregByInstanceID(instanceID string) error {

	_, ok := p.GetServiceByInstanceID(instanceID)
	if !ok {
		logs.Errorf("unregister an unexist service: %s", instanceID)
		return fmt.Errorf("unregister an unexist service: %s", instanceID)
	}
	p.delService(instanceID)

	logs.Debugf("ServiceUnregByInstanceID: %s", instanceID)
	return nil
}

func (p *ServiceMng) removeService(instanceID string) error {

	svc, ok := p.GetServiceByInstanceID(instanceID)
	if !ok {
		logs.Errorf("remove an unexist service: %s", instanceID)
		return fmt.Errorf("remove an unexist service: %s", instanceID)
	}

	svc.End()
	p.delService(instanceID)

	logs.Debugf("removeService: %s", instanceID)

	return nil
}

func (p *ServiceMng) recycleService(params interface{}) {
	for k, v := range p.data {
		if v.GetHealthy() == HealthDEAD {
			registrypubsub.GetPubSub().ClearSub(k)
			p.removeService(k)
		}
	}
}
